package com.norma.spring.dispatcher;

import com.norma.spring.core.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author zhangchaofeng
 * @date 2020/11/6
 * @description
 */
public class NormaDispatcherServlet extends HttpServlet {

    private Properties contextConfig = new Properties();

    /**
     * 保存扫描到的所有类名
     */
    private List<String> classNames = new ArrayList();

    /**
     * 容器
     */
    Map<String, Object> container = new HashMap<>();

    /**
     * 处理器映射
     **/
    Map<String, Method> handlerMapping = new HashMap<>();

    @Override
    public void init(ServletConfig config) {
        try {
            //加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
            //扫描相关类
            doScanner(contextConfig.getProperty("scannerPackage"));
            //初始化扫描到的类 并放入到IOC容器中
            doInstance();
            //完成依赖注入
            doAutowired();
            //初始化HandlerMapping
            initHandlerMapping();
            System.out.println("spring framework is init.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initHandlerMapping() {
        for (Map.Entry<String, Object> entry : container.entrySet()) {
            String beanName = entry.getKey();
            Class clazz = entry.getValue().getClass();
            //跳过非Controller
            if (!clazz.isAnnotationPresent(NormaController.class)) {
                continue;
            }
            String url = "/" + ((NormaController) clazz.getAnnotation(NormaController.class)).value();
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(NormaMapping.class)
                        || "".equals(method.getAnnotation(NormaMapping.class).value())) {
                    continue;
                }
                url = (url.concat("/").concat(method.getAnnotation(NormaMapping.class).value()))
                        .replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.printf("Mapped the url [%s] to method [%s]", url, method);
            }

        }

    }

    private void doAutowired() throws Exception {
        for (Map.Entry<String, Object> entry : container.entrySet()) {
            String beanName = entry.getKey();
            Object instance = entry.getValue();
            for (Field declaredField : instance.getClass().getDeclaredFields()) {
                //跳过没有加注解的属性
                if (!declaredField.isAnnotationPresent(NormaAutowired.class)) {
                    continue;
                }
                NormaAutowired anon = declaredField.getAnnotation(NormaAutowired.class);
                String beanNameDef = anon.value().trim();
                //优先使用自定义的beanName注入
                if ("".equals(beanNameDef)) {
                    //未自定义beanName则使用接口的类型去Container中寻找
                    beanName = declaredField.getType().getName();
                }
                //如果是public以外的修饰符，需要暴力访问
                declaredField.setAccessible(true);
                declaredField.set(instance, container.get(beanName));
            }
        }
    }

    private void doInstance() {
        //初始化，为DI做准备
        if (classNames.isEmpty()) {
            return;
        }
        try {
            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);
                //加了注解的类需要初始化
                if (aClass.isAnnotationPresent(NormaController.class)) {
                    //默认使用class name 为beanName
                    Object instance = aClass.newInstance();
                    String lowerCaseFirstClassName = toLowerFirstCase(aClass.getSimpleName());
                    container.put(lowerCaseFirstClassName, instance);
                } else if (aClass.isAnnotationPresent(NormaService.class)) {
                    String beanName;
                    Object instance = aClass.newInstance();
                    //优先使用自定义的beanName作为名字
                    if (!aClass.getAnnotation(NormaService.class).value().trim().equals("")) {
                        beanName = aClass.getAnnotation(NormaService.class).value().trim();
                        container.put(beanName, instance);
                    } else if (aClass.getInterfaces() != null && aClass.getInterfaces().length != 0) {
                        //使用接口的名字
                        for (Class<?> anInterface : aClass.getInterfaces()) {
                            if (container.containsKey(anInterface.getName())) {
                                //TODO 改成支持多个实现注入
                                throw new Exception("The " + anInterface.getName() + "is duplication.");
                            }
                            container.put(anInterface.getName(), instance);
                        }
                    } else {
                        //使用默认beanName
                        String lowerCaseFirstClassName = toLowerFirstCase(aClass.getSimpleName());
                        container.put(lowerCaseFirstClassName, instance);
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String toLowerFirstCase(String simpleName) {
        //大小写字母的ASCII相差32 大写字母的ASCII小于小写字母
        //在char中做算学运算，其实就是对ASCII做算学运算
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doScanner(String scannerPackage) {
        //scannerPackage=com.norma.spring
        //转换为包路径
        URL resource = this.getClass().getClassLoader().getResource("/" + scannerPackage
                .replaceAll("\\.", "/"));
        File classPath = new File(resource.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scannerPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = scannerPackage + "." + file.getName()
                        .replace(".class", "");
                classNames.add(className);
            }
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        String url = requestURI.replace(contextPath, "").replaceAll("/+", "/");
        if (!handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 Not Found.");
            resp.getWriter().println("404 Not Found.");
        }
        Method method = handlerMapping.get(url);
        String clazzName = method.getDeclaringClass().getSimpleName();
        //获取请求实参列表
        Map<String, String[]> parameterMap = req.getParameterMap();
        //获取方法的形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        List<Object> paramValues = new ArrayList();
        Integer index = 0;
        for (Class<?> parameterType : parameterTypes) {
            if (parameterType == HttpServletRequest.class) {
                paramValues.add(req);
                continue;
            } else if (parameterType == HttpServletResponse.class) {
                paramValues.add(resp);
                continue;
            } else if (parameterType == String.class) {
                //TODO 多类型适配
                for (Annotation annotation : parameterAnnotations[index++]) {
                    if (annotation instanceof NormaParam) {
                        String paramName = ((NormaParam) annotation).value();
                        if (parameterMap.containsKey(paramName)) {
                            String[] paramValue = parameterMap.get(paramName);
                            paramValues.add(paramValue[0]);
                        }
                    }
                }
            }
        }
        method.invoke(container.get(toLowerFirstCase(clazzName)),
                paramValues.toArray());

    }



    private void doLoadConfig(String contextConfigLocation) throws IOException {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        contextConfig.load(resourceAsStream);
        if (resourceAsStream != null) {
            resourceAsStream.close();
        }
    }


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
